/*
 * booleanIndex.cpp
 *
 *  Created on: Dec 15, 2012
 *      Author: m
 */

#include <iostream>
#include "application_commons.h"
#include "../logic_layer/bplustree/BPlusTreeFixed.h"
#include "../physical_layer/file_system/TermSequentialFile.h"
#include "../physical_layer/file_system/InvertedListFile.h"
#include "../physical_layer/file_system/PhraseDictionary.h"
#include "../physical_layer/file_system/TermOccurrence.h"
#include "../physical_layer/file_system/TermOccurrenceSequentialFile.h"
#include "../physical_layer/file_system/Phrase.h"
#include "../physical_layer/file_system/InvertedList.h"
#include "../logic_layer/external_sort/ExternalSort.h"


using physical::TermOccurrenceSequentialFile;
using physical::TermSequentialFile;
using physical::InvertedListFile;
using physical::PhraseDictionary;
using physical::TermOccurrence;
using physical::InvertedList;
using physical::Phrase;
using std::string;
using std::list;

/**
 * Programa para controlar el índice booleano
 *
 * Uso: booleanIndex COMANDO [OPCIONES]
 *
 *
 * Comandos:
 *
 *  -help : Este mensaje de ayuda
 *
 *  -usage : idem -help
 *
 *  -construir :
 *      Construye un nuevo índice booleano a partir del diccionario
 *       de frases ( creado con la aplicación phraseDictionary ),
 *       cualquier información previa que tenga el índice es eliminada.
 *       Nota: Tener en cuenta que el diccionario de frases debe
 *       llamarse boolean.phrasedictionary.dat
 *
 *  -agregar <sub-comandos> :
 *      Agrega una nueva frase al diccionario y la indexa.
 *
 *      Los sub-comandos son:
 *          -texto "nueva frase": Texto de la frase a cargar
 *          -nombre "nombre del autor"
 *          -apellido "apellido del autor"
 *
 *      Nota: el subcomando -texto es obligatorio! Los demás son opcionales
 *
 *  -modificar idFrase <sub-comandos> :
 *      Modifica la frase existente con id = idFrase
 *
 *      Los sub-comandos son:
 *          -texto "nueva frase": Texto de la frase a cargar
 *          -nombre "nombre del autor"
 *          -apellido "apellido del autor"
 *
 *      Nota: Al menos un sub-comando debe ser ingresado.
 *
 *  -eliminar idFrase:
 *      Elimina la frase existente con id = idFrase.
 *
 *  -consultar "consulta":
 *      Devuelve todos los resultados que tengan un match con la consulta.
 *      El formato para realizar consultas es el siguiente:
 *          AND: "termino1 & termino2 & termino3"
 *          OR:  "termino1 | termino2 | termino3"
 *          NOT: "~termino1 ~termino2 ~termino3"
 */

const char stopWordsDictionaryFileName[]   = "stop-words.txt";
const char phraseDictionaryFileName[]      = "booleanIndex.phraseDictionary.dat";
const char bPlusTreeFileName[]             = "booleanIndex.bPlusTree.dat";
const char termsInAparitionOrderFileName[] = "booleanIndex.termsInAparitionOrder.dat";
const char termOcurrenceFileName[]         = "booleanIndex.termOcurrence.dat";
const char termOcurrenceOrderedFileName[]  = "booleanIndex.termOcurrenceOrdered.dat";
const char invertedListFileName[]          = "booleanIndex.invertedList.dat";
const char queryResultFileName[]           = "booleanIndex.queryResult.dat";
const char logFileName[]                   = "booleanIndex.log";

int main ( int argc, const char *argv[] )
{
    unsigned short lastOcurrenceTermId;
    size_t bufferSize = 128;
    unsigned short phraseId;
    bool rebuildIndex;
    options_t options;
    PhraseDictionary phraseDictionary;
    PhraseDictionary::Record record;
    list< string > relevantTerms;
    TermSequentialFile termsFile;
    TermOccurrenceSequentialFile termOcurrenceFile;
    TermOccurrenceSequentialFile termOcurrenceOrderedFile;
    InvertedList invertedList;
    list< unsigned short > newPhraseTermIdList;
    list< unsigned short > oldPhraseTermIdList;
    list< InvertedList > queryInvertedLists;

    parse_arguments( argc, argv, options );

    rebuildIndex = options.mode == system_mode::BUILD;

    BPlusTreeFixed tree( bPlusTreeFileName, 128, rebuildIndex );

    InvertedListFile invertedListFile( invertedListFileName, rebuildIndex );

    ofstream log( logFileName, std::ios_base::app );

    switch ( options.mode )
    {
        case system_mode::BUILD:
        {

            log << "************************************" << endl;
            log << "*** Construyendo Indice Booleano ***" << endl;
            log << "************************************" << endl;

            /*
             * Inicialización
             */
            loadStopWords( stopWordsDictionaryFileName, stopWordsDictionary );

            phraseDictionary.open( phraseDictionaryFileName,
                    std::ios_base::in | std::ios_base::out );

            termsFile.open( termsInAparitionOrderFileName,
                    std::ios_base::in | std::ios_base::out | std::ios_base::trunc );

            termOcurrenceFile.open( termOcurrenceFileName,
                    std::ios_base::in | std::ios_base::out | std::ios_base::trunc );

            ExternalSort sorter(bufferSize, string(termOcurrenceFileName), string(termOcurrenceOrderedFileName), false);

            /*
             * Creo un nuevo índice a partir de un diccionario de frases
             */

            record = phraseDictionary.getFirst();

            do
            {
                Phrase phrase = record.get();
                phraseId = record.getPosition();

                // extraigo los términos relevantes
                relevantTerms = extractRelevantTerms( phrase );

                /*
                 * Por cada término relevante:
                 * - Lo busco en el árbol B+
                 *      Si no está:
                 *          Lo meto en el archivo de terminos por orden de aparición
                 *          Con eso obtengo su ID
                 *          Lo meto en el árbol
                 *
                 *      Si está obtengo su ID.
                 *
                 * - Agrego la Ocurrencia en el archivo de ocurrencias
                 */

                for ( auto it = relevantTerms.begin(); it != relevantTerms.end(); it++ )
                {
                    unsigned short termId;

                    // Buscarlo en el arbol B+
                    auto queryResult = tree.SearchRegister( *it );

                    if ( !queryResult.valid )
                    {
                        /*
                         * Lo guardo al final del archivo de terminos por orden de aparición
                         *  y obtengo su posición relativa en dicho archivo
                         */
                        termId = termsFile.put( *it );

                        /*
                         * Lo registro en el árbol B+ con su posición relativa en el archivo
                         *  de terminos por orden de aparición
                         */
                        tree.InsertRegister( *it, { termId, 0 } );
                    }
                    else
                    {
                        /*
                         * Obtengo su posición relativa en el archivo
                         *  de terminos por orden de aparición del árbol B+
                         */
                        termId = queryResult.result->getValue().m_termPosition;
                    }

                    log << "ocurrencia: ( " << termId << ", " << phraseId << " ) termino: " << *it << endl;

                    /*
                     * Agrego la Ocurrencia en el archivo de ocurrencias
                     */
                    termOcurrenceFile.put( TermOccurrence( termId, phraseId ) );

                }
            }
            while ( phraseDictionary.hasNext(record)
                    && ( record = phraseDictionary.getNext(record) ) == record );

            /*
             * Aplico el sort-externo al archivo de ocurrencias ( El resultado final se
             *  guarda en donde indica la variable termOcurrenceOrderedFileName )
             */
            termOcurrenceFile.close();
            termsFile.close();

            sorter.sort();

            /*
             * Voy leyendo una a una las ocurrencias y voy armando la lista invertida
             *  para el término indicado, cuando cambia el idTerm de la ocurrencia leida
             *  persisto la anterior en el archivo de listas invertidas y registro dicha
             *  lista en el árbol B+
             */
            termOcurrenceOrderedFile.open( termOcurrenceOrderedFileName,
                    std::ios_base::in | std::ios_base::out );

            termsFile.open( termsInAparitionOrderFileName,
                    std::ios_base::in | std::ios_base::out );

            lastOcurrenceTermId = 0;

            while ( termOcurrenceOrderedFile.hasNext() )
            {
                auto ocurrence = termOcurrenceOrderedFile.get();

                log << "Procesando ocurrencia: ( " << ocurrence.getTermId()
                    << ", " << ocurrence.getPhraseId() << " )" << endl;


                if ( ocurrence.getTermId() != lastOcurrenceTermId && lastOcurrenceTermId != 0 )
                {
                    log << "Procesando lista invertida" << endl;

                    // Recupero el término del archivo correspondiente
                    auto term = termsFile.get();

                    log << "termId: "  << lastOcurrenceTermId << endl;

                    log << "term: " << term.getTerm() << endl;

                    log << "lista invertida: ";

                    for ( auto it = invertedList.begin(); it!=invertedList.end(); it++ )
                    {
                        log << *it << " ";
                    }
                    log << endl;

                    log << "persistiendo lista invertida" << endl;

                    // Persisto la lista invertida
                    unsigned int invertedListBlock =
                            invertedListFile.addList( lastOcurrenceTermId, invertedList );

                    log << "lista persistida" << endl;


                    log << "bloque de la lista invertida: " << invertedListBlock << endl;

                    log << "Actualizando árbol" << endl;

                    // Actualizo el árbol B+ con el bloque en donde esta la lista invertida
                    tree.UpdateRegister( term.getTerm(), invertedListBlock );
                    log << "Árbol actualizado con éxito" << endl;

                    invertedList.clear();
                }

                invertedList.push_back( ocurrence.getPhraseId() );

                lastOcurrenceTermId = ocurrence.getTermId();
            }

            // Persisto la última lista invertida
            if ( lastOcurrenceTermId != 0 )
            {
                log << "Procesando lista invertida" << endl;

                // Recupero el término del archivo correspondiente
                auto term = termsFile.get();

                log << "termId: "  << lastOcurrenceTermId << endl;

                log << "term: " << term.getTerm() << endl;

                log << "lista invertida: ";

                for ( auto it = invertedList.begin(); it!=invertedList.end(); it++ )
                {
                    log << *it << " ";
                }
                log << endl;

                log << "persistiendo lista invertida" << endl;

                // Persisto la lista invertida
                auto invertedListBlock =
                        invertedListFile.addList( lastOcurrenceTermId, invertedList );

                log << "lista persistida" << endl;

                log << "bloque de la lista invertida: " << invertedListBlock << endl;

                log << "Actualizando árbol" << endl;

                // Actualizo el árbol B+ con el bloque en donde esta la lista invertida
                tree.UpdateRegister( term.getTerm(), invertedListBlock );
                log << "Árbol actualizado con éxito" << endl;

                invertedList.clear();
            }

            break;
        }

        case system_mode::ADD:
        {
            log << "************************************" << endl;
            log << "***   Añadiendo una nueva frase  ***" << endl;
            log << "************************************" << endl;

            log << "frase: "    << options.newText          << endl;
            log << "nombre: "   << options.newAuthorName    << endl;
            log << "apellido: " << options.newAuthorSurname << endl;

            /*
             * Añado una nueva frase al diccionario y luego la indexo
             */
            loadStopWords( stopWordsDictionaryFileName, stopWordsDictionary );

            phraseDictionary.open( phraseDictionaryFileName,
                    std::ios_base::in | std::ios_base::out );

            termsFile.open( termsInAparitionOrderFileName,
                    std::ios_base::in | std::ios_base::out );

            Phrase phrase( options.newText, options.newAuthorSurname, options.newAuthorName );

            log << "insertando frase en el diccionario" << endl;

            phraseId = phraseDictionary.add( phrase );

            log << "frase insertada en la posición: " << phraseId << endl;

            // extraigo los términos relevantes
            relevantTerms = extractRelevantTerms( phrase );

            /*
             * Por cada término relevante:
             * - Lo busco en el árbol B+
             *      Si no está:
             *          Lo meto en el archivo de terminos por orden de aparición
             *          Con eso obtengo su ID y le asigno nº de bloque 0
             *          Guardo el término en el árbol
             *
             *      Si está :
             *          Obtengo su ID y el nº de bloque de lista invertida
             *
             *
             * Verifico si Nº bloque == 0
             *  Si es 0:
             *      Persisto la nueva lista invertida, y obtengo su nº de bloque
             *
             *  Si no es cero:
             *      Leo la lista invertida
             *      Le agrego el IdDoc nuevo
             *      La actualizo
             *
             * Verifico si cambio el nº de bloque
             *  Si cambio: Actualizo el arbol
             *  Si no cambio: No hago nada
             */

            for ( auto it = relevantTerms.begin(); it != relevantTerms.end(); it++ )
            {
                unsigned short termId;
                unsigned int invertedListPosition = 0;

                log << "Procesando término relevante: " << *it << endl;

                // Buscarlo en el arbol B+
                auto queryResult = tree.SearchRegister( *it );

                if ( !queryResult.valid )
                {
                    log << "Insertando nuevo término en el indice: " << *it << endl;

                    /*
                     * Lo guardo al final del archivo de terminos por orden de aparición
                     *  y obtengo su posición relativa en dicho archivo
                     */
                    termId = termsFile.put( *it );

                    log << "Id del nuevo término: " << termId << endl;

                    /*
                     * Lo registro en el árbol B+ con su posición relativa en el archivo
                     *  de terminos por orden de aparición
                     */
                    tree.InsertRegister( *it, { termId, invertedListPosition } );
                }
                else
                {
                    log << "Recuperando información del indice para: " << *it << endl;

                    /*
                     * Obtengo su posición relativa en el archivo
                     *  de terminos por orden de aparición del árbol B+
                     */
                    termId = queryResult.result->getValue().m_termPosition;

                    log << "el id del término es: " << termId << endl;

                    invertedListPosition = queryResult.result->getValue().m_invertedListPosition;

                    log << "El bloque en el que está la inverted list del término es: "
                            << invertedListPosition << endl;
                }

                if ( invertedListPosition == 0 )
                {
                    log << "creando nueva lista invertida" << endl;

                    // Agrego la Ocurrencia en una lista invertida en RAM
                    invertedList.clear();
                    invertedList.push_back( phraseId );

                    log << "nueva lista invertida: ";
                    for ( auto elem = invertedList.begin(); elem != invertedList.end(); elem++ )
                    {
                        log << *elem << " ";
                    }
                    log << endl;

                    log << "persistiendo lista invertida" << endl;

                    // Persisto la nueva lista invertida
                    invertedListPosition = invertedListFile.addList( termId, invertedList );

                    log << "lista persistida con éxito, en bloque: " << invertedListPosition << endl;

                    log << "actualizando árbol B+" << endl;

                    // Actualizo el árbol B+ con el bloque en donde esta la lista invertida
                    tree.UpdateRegister( *it , invertedListPosition );

                    log << "Árbol B+ actualizado con éxito" << endl;
                }
                else
                {
                    log << "recuperando lista invertida" << endl;

                    invertedList = invertedListFile.getList( termId, invertedListPosition );

                    log << "lista invertida recuperada con éxito" << endl;

                    log << "lista invertida recuperada: ";
                    for ( auto elem = invertedList.begin(); elem != invertedList.end(); elem++ )
                    {
                        log << *elem << " ";
                    }
                    log << endl;

                    log << "insertando nuevo elemento en la lista invertida: " << phraseId << endl;

                    invertedList.push_back( phraseId );

                    log << "lista invertida final: ";
                    for ( auto elem = invertedList.begin(); elem != invertedList.end(); elem++ )
                    {
                        log << *elem << " ";
                    }
                    log << endl;

                    log << "persistiendo nueva lista invertida" << endl;

                    // Persisto la nueva lista invertida
                    unsigned int newInvertedListPosition =
                            invertedListFile.updateList( termId, invertedListPosition, invertedList );

                    log << "lista invertida persistida con éxito en el bloque: "
                            << newInvertedListPosition << endl;

                    /*
                     * Si cambió el bloque donde está la lista invertida, actualizo el árbol B+
                     *  con el nuevo valor de bloque
                     */
                    if ( newInvertedListPosition != invertedListPosition )
                    {
                        log << "detectado cambio de bloque de la lista invertida" << endl;

                        log << "actualizando árbol B+" << endl;

                        tree.UpdateRegister( *it , newInvertedListPosition );

                        log << "árbol B+ actualizado con éxito" << endl;
                    }
                }
            }
            break;
        }

        case system_mode::UPDATE:
        {
            /*
             * Actualizo una frase del diccionario y actualizo el índice
             */

            log << "************************************" << endl;
            log << "***      Actualizando frase      ***" << endl;
            log << "************************************" << endl;

            loadStopWords( stopWordsDictionaryFileName, stopWordsDictionary );

            phraseDictionary.open( phraseDictionaryFileName,
                    std::ios_base::in | std::ios_base::out );

            termsFile.open( termsInAparitionOrderFileName,
                    std::ios_base::in | std::ios_base::out );

            /*
             * Creo la frase nueva
             */
            Phrase newPhrase( options.newText, options.newAuthorSurname, options.newAuthorName );

            phraseId = options.phraseId;

            /*
             * Traer la frase vieja del diccionario
             */
            auto oldPhrase = phraseDictionary.get( phraseId ).get();

            // Extraer los términos relevantes de ambas frases
            auto oldPhraseRelevantTerms = extractRelevantTerms( oldPhrase );
            auto newPhraseRelevantTerms = extractRelevantTerms( newPhrase );

            // Obtener los términos a eliminar y los términos a agregar
            auto termsToRemove = oldPhraseRelevantTerms;
            auto termsToAdd = newPhraseRelevantTerms;

            list < list < string >::iterator > iteratorToTermsToRemove;
            list < list < string >::iterator > iteratorToTermsToAdd;

            for ( auto it = termsToRemove.begin(); it != termsToRemove.end(); it++ )
            {
                for ( auto it2 = newPhraseRelevantTerms.begin(); it2 != newPhraseRelevantTerms.end(); it2++ )
                {
                    if ( *it == *it2 )
                    {
                        iteratorToTermsToRemove.push_back( it );
                    }
                }
            }

            for ( auto it = iteratorToTermsToRemove.begin(); it != iteratorToTermsToRemove.end(); it++ )
            {
                termsToRemove.erase( *it );
            }

            for ( auto it = termsToAdd.begin(); it != termsToAdd.end(); it++ )
            {
                for ( auto it2 = oldPhraseRelevantTerms.begin(); it2 != oldPhraseRelevantTerms.end(); it2++ )
                {
                    if ( *it == *it2 )
                    {
                        iteratorToTermsToAdd.push_back( it );
                    }
                }
            }

            for ( auto it = iteratorToTermsToAdd.begin(); it != iteratorToTermsToAdd.end(); it++ )
            {
                termsToAdd.erase( *it );
            }

            // Eliminar y agregar los términos según corresponda

            for ( auto it = termsToAdd.begin(); it != termsToAdd.end(); it++ )
            {
                unsigned short termId;
                unsigned int invertedListPosition = 0;

                log << "Procesando término relevante: " << *it << endl;

                // Buscarlo en el arbol B+
                auto queryResult = tree.SearchRegister( *it );

                if ( !queryResult.valid )
                {
                    log << "Insertando nuevo término en el indice: " << *it << endl;

                    /*
                     * Lo guardo al final del archivo de terminos por orden de aparición
                     *  y obtengo su posición relativa en dicho archivo
                     */
                    termId = termsFile.put( *it );

                    log << "Id del nuevo término: " << termId << endl;

                    /*
                     * Lo registro en el árbol B+ con su posición relativa en el archivo
                     *  de terminos por orden de aparición
                     */
                    tree.InsertRegister( *it, { termId, invertedListPosition } );
                }
                else
                {
                    log << "Recuperando información del indice para: " << *it << endl;

                    /*
                     * Obtengo su posición relativa en el archivo
                     *  de terminos por orden de aparición del árbol B+
                     */
                    termId = queryResult.result->getValue().m_termPosition;

                    log << "el id del término es: " << termId << endl;

                    invertedListPosition = queryResult.result->getValue().m_invertedListPosition;

                    log << "El bloque en el que está la inverted list del término es: "
                            << invertedListPosition << endl;
                }

                if ( invertedListPosition == 0 )
                {
                    log << "creando nueva lista invertida" << endl;

                    // Agrego la Ocurrencia en una lista invertida en RAM
                    invertedList.clear();
                    invertedList.push_back( phraseId );

                    log << "nueva lista invertida: ";
                    for ( auto elem = invertedList.begin(); elem != invertedList.end(); elem++ )
                    {
                        log << *elem << " ";
                    }
                    log << endl;

                    log << "persistiendo lista invertida" << endl;

                    // Persisto la nueva lista invertida
                    invertedListPosition = invertedListFile.addList( termId, invertedList );

                    log << "lista persistida con éxito, en bloque: " << invertedListPosition << endl;

                    log << "actualizando árbol B+" << endl;

                    // Actualizo el árbol B+ con el bloque en donde esta la lista invertida
                    tree.UpdateRegister( *it , invertedListPosition );

                    log << "Árbol B+ actualizado con éxito" << endl;
                }
                else
                {
                    log << "recuperando lista invertida" << endl;

                    invertedList = invertedListFile.getList( termId, invertedListPosition );

                    log << "lista invertida recuperada con éxito" << endl;

                    log << "lista invertida recuperada: ";
                    for ( auto elem = invertedList.begin(); elem != invertedList.end(); elem++ )
                    {
                        log << *elem << " ";
                    }
                    log << endl;

                    log << "insertando nuevo elemento en la lista invertida: " << phraseId << endl;

                    invertedList.push_back( phraseId );

                    log << "lista invertida final: ";
                    for ( auto elem = invertedList.begin(); elem != invertedList.end(); elem++ )
                    {
                        log << *elem << " ";
                    }
                    log << endl;

                    log << "persistiendo nueva lista invertida" << endl;

                    // Persisto la nueva lista invertida
                    unsigned int newInvertedListPosition =
                            invertedListFile.updateList( termId, invertedListPosition, invertedList );

                    log << "lista invertida persistida con éxito en el bloque: "
                            << newInvertedListPosition << endl;

                    /*
                     * Si cambió el bloque donde está la lista invertida, actualizo el árbol B+
                     *  con el nuevo valor de bloque
                     */
                    if ( newInvertedListPosition != invertedListPosition )
                    {
                        log << "detectado cambio de bloque de la lista invertida" << endl;

                        log << "actualizando árbol B+" << endl;

                        tree.UpdateRegister( *it , newInvertedListPosition );

                        log << "árbol B+ actualizado con éxito" << endl;
                    }
                }
            }

            for ( auto term = termsToRemove.begin(); term != termsToRemove.end(); term++ )
            {
                log << "eliminando término de la frase: " << *term << std::endl;

                auto treeEntry = tree.SearchRegister( *term );

                unsigned int invertedListPosition = treeEntry.result->getValue().m_invertedListPosition;
                size_t termId = treeEntry.result->getValue().m_termPosition;

                log << "idTerm: " << termId << endl;
                log << "bloque de la lista invertida: " << invertedListPosition << endl;

                invertedList = invertedListFile.getList( termId, invertedListPosition );

                auto phraseIt = invertedList.begin();
                for ( ; phraseIt != invertedList.end(); phraseIt++ )
                {
                    if ( *phraseIt == options.phraseId )
                    {
                        break;
                    }
                }

                invertedList.erase( phraseIt );

                if ( !invertedList.empty() )
                {
                    invertedListFile.updateList( termId, invertedListPosition, invertedList );
                }
                else
                {
                    // Borrar la lista invertida
                    invertedListFile.removeList( termId, invertedListPosition );

                    // Actualizar el Árbol B+ con un 0
                    tree.UpdateRegister( *term , 0 );
                }
            }

            // Actualizar la frase en el diccionario
            phraseDictionary.update( phraseId, newPhrase );

            break;
        }

        case system_mode::REMOVE:
        {
            /*
             * Elimino una frase del diccionario y actualizo el índice
             */

            log << "************************************" << endl;
            log << "***     Eliminando una frase     ***" << endl;
            log << "************************************" << endl;

            loadStopWords( stopWordsDictionaryFileName, stopWordsDictionary );

            phraseDictionary.open( phraseDictionaryFileName,
                    std::ios_base::in | std::ios_base::out );

            /*
             * Traer la frase del diccionario
             */
            auto phrase = phraseDictionary.get( options.phraseId ).get();

            /*
             * Extraer los términos relevantes
             */
            auto relevantTerms = extractRelevantTerms( phrase );

            log << "procesando términos relevantes" << std::endl;

            /*
             * Por cada término relevante:
             *
             *  Buscar en el arbol el nº de bloque y el id del término
             *
             *      Leer la lista invertida
             *
             *      Eliminar el id de Documento de cada lista invertida
             *
             *      Si la lista invertida no esta vacia:
             *
             *          Actualizar la lista invertida
             *
             *      Si está vacia
             *
             *          Borrar la lista invertida
             *
             *          Actualizar el Árbol B+ con un 0
             */
            for ( auto term = relevantTerms.begin(); term != relevantTerms.end(); term++ )
            {
                log << "eliminando término de la frase: " << *term << std::endl;

                auto treeEntry = tree.SearchRegister( *term );

                unsigned int invertedListPosition = treeEntry.result->getValue().m_invertedListPosition;
                size_t termId = treeEntry.result->getValue().m_termPosition;

                log << "idTerm: " << termId << endl;
                log << "bloque de la lista invertida: " << invertedListPosition << endl;

                invertedList = invertedListFile.getList( termId, invertedListPosition );

                auto phraseIt = invertedList.begin();
                for ( ; phraseIt != invertedList.end(); phraseIt++ )
                {
                    if ( *phraseIt == options.phraseId )
                    {
                        break;
                    }
                }

                invertedList.erase( phraseIt );

                if ( !invertedList.empty() )
                {
                    invertedListFile.updateList( termId, invertedListPosition, invertedList );
                }
                else
                {
                    // Borrar la lista invertida
                    invertedListFile.removeList( termId, invertedListPosition );

                    // Actualizar el Árbol B+ con un 0
                    tree.UpdateRegister( *term , 0 );
                }
            }

            /*
             * Borro la frase del diccionario
             */
            phraseDictionary.remove( options.phraseId );

            break;
        }
        case system_mode::QUERY:
        {
            /*
             * Realizo una consulta
             */

            list < unsigned short > queryResults;

            loadStopWords( stopWordsDictionaryFileName, stopWordsDictionary );

            phraseDictionary.open( phraseDictionaryFileName,
                    std::ios_base::in | std::ios_base::out );

            /**
             * ACÁ SE EMPIEZA A COMPUTAR EL TIEMPO DE CONSULTA
             */
            auto startTime = getTimeInMicroSeconds();


            // Parseo la query
            query theQuery = parseQuery( options.query );

            // Valido el resultado de la query
            if ( ( theQuery.queryType == query_type::INVALID_QUERY ) || theQuery.termList.empty() )
            {
                invalid_query();
            }

            // Obtengo las listas invertidas de cada término
            for ( auto it = theQuery.termList.begin(); it != theQuery.termList.end(); it++ )
            {
                auto queryResult = tree.SearchRegister( *it );

                /*
                 * Si el término de búsqueda no existe o no tiene lista invertida
                 *  simulo una inverted list vacia
                 */
                if ( !queryResult.valid || queryResult.result->getValue().m_invertedListPosition == 0 )
                {
                    invertedList.clear();
                }
                else
                {
                    auto termId = queryResult.result->getValue().m_termPosition;
                    auto invertedListBlock = queryResult.result->getValue().m_invertedListPosition;

                    invertedList = invertedListFile.getList( termId, invertedListBlock );
                }

                queryInvertedLists.push_back( invertedList );
            }


            // Resuelvo la consulta
            switch ( theQuery.queryType )
            {
                case query_type::SIMPLE_QUERY:

                    queryResults = list< unsigned short > (
                            queryInvertedLists.front().begin(), queryInvertedLists.front().end() );

                    break;

                case query_type::AND_QUERY:
                {

                    /*
                     * Esto no es muy prolijo, ni optimo, pero es una solucion para salir del paso
                     */

                    list < unsigned short > fullList;

                    // Agrego todos los elementos en una sola lista
                    for ( auto invList = queryInvertedLists.begin(); invList != queryInvertedLists.end(); invList++ )
                    {
                        for ( auto elem = invList->begin(); elem != invList->end(); elem++ )
                        {
                            fullList.push_back( *elem );
                        }
                    }

                    fullList.sort();
                    fullList.unique();

                    /*
                     * Luego recorro la lista con todos los elementos y solo me quedo con los que
                     *  aparecen en todas las listas
                     */
                    for ( auto elem = fullList.begin(); elem != fullList.end(); elem++ )
                    {
                        size_t elementFoundCounter = 0;

                        for ( auto invList = queryInvertedLists.begin(); invList != queryInvertedLists.end(); invList++ )
                        {
                            for ( auto it = invList->begin(); it != invList->end(); it++ )
                            {
                                if ( *it == *elem )
                                {
                                    elementFoundCounter++;
                                    break;
                                }
                            }
                        }

                        if ( elementFoundCounter == queryInvertedLists.size() )
                        {
                            queryResults.push_back( *elem );
                        }
                    }

                    break;
                }

                case query_type::OR_QUERY:

                    for ( auto it = queryInvertedLists.begin(); it != queryInvertedLists.end(); it++ )
                    {
                        for ( auto elem = it->begin(); elem != it->end(); elem++ )
                        {
                            queryResults.push_back( *elem );
                        }
                    }

                    break;

                case query_type::NOT_QUERY:

                    // Obtener el id de todos los elementos

                    /*
                    queryResults = list< unsigned short > (
                            queryInvertedLists.front().begin(), queryInvertedLists.front().end() );
                    */
                    for ( auto it = queryInvertedLists.begin(); it != queryInvertedLists.end(); it++ )
                    {
                        for ( auto elem = it->begin(); elem != it->end(); elem++ )
                        {
                            queryResults.push_back( *elem );
                        }
                    }
                    break;

                case query_type::INVALID_QUERY:
                    invalid_query();
            }

            queryResults.sort();
            queryResults.unique();

            /**
             * ACÁ SE TERMINA DE COMPUTAR EL TIEMPO DE CONSULTA
             */
            auto stopTime = getTimeInMicroSeconds();

            ofstream queryResultFile( queryResultFileName );

            // Imprimo la consulta
            queryResultFile << "CONSULTA: "<< options.query << std::endl;

            // Imprimo el tiempo de procesamiento
            queryResultFile << "TIEMPO DE PROCESAMIENTO: " <<
                    stopTime - startTime << " us" << std::endl;

            // Imprimo la lista de resultados
            if ( queryResults.empty() && theQuery.queryType != query_type::NOT_QUERY )
            {
                queryResultFile << "NO SE ENCONTRARON RESULTADOS" << std::endl;
            }
            else
            {
                queryResultFile << "RESULTADOS:" << std::endl;
                if ( theQuery.queryType != query_type::NOT_QUERY )
                {
                    for ( auto it = queryResults.begin(); it != queryResults.end(); it++ )
                    {

                        // Leo la frase del archivo
                        auto result = phraseDictionary.get( *it );

                        queryResultFile << result.getPosition() << " "
                                << result.get().getText()       << " "
                                << result.get().getAuthorName() << " "
                                << result.get().getAuthorSurname() << endl;
                    }
                }
                else
                {
                    // Leer todas las frases e imprimir solo las que no están en la lista

                    PhraseDictionary::Record record = phraseDictionary.getFirst();

                    do
                    {
                        bool skipRecord = false;

                        for ( auto it = queryResults.begin(); it != queryResults.end(); it++ )
                        {
                            if ( *it == record.getPosition() )
                            {
                                skipRecord = true;
                            }
                        }

                        if ( skipRecord == false )
                        {
                            Phrase phrase = record.get();

                            queryResultFile << record.getPosition() << " "
                                    << record.get().getText()       << " "
                                    << record.get().getAuthorName() << " "
                                    << record.get().getAuthorSurname() << endl;
                        }
                    }
                    while ( phraseDictionary.hasNext(record)
                            && (record = phraseDictionary.getNext(record)) == record );

                }
            }

            queryResultFile.close();

            break;
        }

        case system_mode::INVALID:

            invalid_args();
            break;
    }

    return 0;
}
